//################################################################################
// MIT License
// Copyright (c) 2024 ZhangYihua
//
// Change Logs:
// Date           Author       Notes
// 2020-10-30     ZhangYihua   first version
//
// Description  : faster trace level signal with accelerated speed
//################################################################################
//  
//           2^(-kp)
//            |            /---<----\
//    in_dat  V            |        |      out_dat
// --------->(*)-(st)-[D]-(+)-(s)->[D]--(t)--------->
//      |    ______        ^      oacc_ff
//       -->|      |       |
// 2^(-ki)  |u_iacc|-------
// -------->|______|  sts_iacc tend to 0 after loop is stable
//           
module pi_acc #(    // proportional and integral accumulator 
parameter           IN_DW                   = 10,
parameter           IN_FW                   = 9,
parameter           OUT_DW                  = 12,
parameter           OUT_FW                  = 9,    // must OUT_FW<=OACC_FW
parameter           OACC_FW                 = 24,   // must OACC_FW<=IN_FW+KI_MAX
parameter           PINC_DW                 = 12,   // must PINC_DW<OUT_DW-OUT_FW+OACC_FW, and PINC_DW<=OACC_FW+IN_DW-IN_FW-KP_MIN
parameter           IINC_DW                 = 10,   // must IINC_DW<OUT_DW-OUT_FW+OACC_FW, IINC_DW means accelerated speed, must be tiny
parameter           IACC_FW                 = 27,   // must OACC_FW<=IACC_FW<=IN_FW+KI_MAX
parameter           KP_MAX                  = 15,
parameter           KP_MIN                  = 8,
parameter           KI_MAX                  = 23,   // must KI_MAX>=KP_MAX
parameter           KI_MIN                  = 12,

// the following parameters are calculated automatically
parameter           KPW                     = $clog2(KP_MAX+1), // range [KP_MIN:KP_MAX]
parameter           KIW                     = $clog2(KI_MAX+1)  // range [KI_MIN:KI_MAX]
) ( 
input                                       rst_n,
input                                       clk,
input                                       cke,

input                                       in_vld,
input       signed  [IN_DW-1:0]             in_dat,     // s(  IN_DW,   IN_FW)
output      signed  [OUT_DW-1:0]            out_dat,    // s( OUT_DW,  OUT_FW)

input               [KPW-1:0]               cfg_kp,     // range [KP_MIN:KP_MAX], gain = 2^(-kp)
input               [KIW-1:0]               cfg_ki,     // range [KI_MIN:KI_MAX], gain = 2^(-ki)

input                                       cfg_oacc_clr,   // clear regs ot ini
input                                       cfg_oacc_frz,
input       signed  [OUT_DW-1:0]            cfg_oacc_ini,   // s( OUT_DW,  OUT_FW), initial value
output      signed  [OUT_DW-1:0]            sts_oacc_rtv,   // s( OUT_DW,  OUT_FW), real-time value

input                                       cfg_iacc_clr,   // clear regs ot ini
input                                       cfg_iacc_frz,
input       signed  [IINC_DW-1:0]           cfg_iacc_ini,   // s(IINC_DW, IINC_FW), initial value
output      signed  [IINC_DW-1:0]           sts_iacc_rtv    // s(IINC_DW, IINC_FW), real-time value
);

//################################################################################
// define local varialbe and localparam
//################################################################################
localparam          IN_IW                   = IN_DW-IN_FW;      // IN_IW<=0 is legal
localparam          OUT_IW                  = OUT_DW-OUT_FW;    // OUT_IW<=0 is legal
localparam          OACC_IW                 = OUT_IW;
localparam          OACC_DW                 = OACC_IW+OACC_FW;
localparam          INC_DW                  = MAX_F(IINC_DW, PINC_DW)+1;
localparam          INC_FW                  = OACC_FW;

reg                                         inc_vld;
wire        signed  [INC_DW-1:0]            inc_dat;

//################################################################################
// main
//################################################################################

// out_dat = in_dat*2^(-kp) + iacc; iacc = iacc + in_dat*2^(-ki);
pi_ctrl #(    // proportional and integral accumulator 
        .IN_DW                          (IN_DW                          ),
        .IN_FW                          (IN_FW                          ),
        .OUT_FW                         (INC_FW                         ),	// must OUT_FW<=IN_FW+KI_MAX
        .PINC_DW                        (PINC_DW                        ),	// must PINC_DW<=OUT_FW+IN_DW-IN_FW-KP_MIN
        .IINC_DW                        (IINC_DW                        ),
        .IACC_FW                        (IACC_FW                        ),	// must OUT_FW<=IACC_FW<=IN_FW+KI_MAX
        .KP_MAX                         (KP_MAX                         ),
        .KP_MIN                         (KP_MIN                         ),
        .KI_MAX                         (KI_MAX                         ),	// must KI_MAX>=KP_MAX
        .KI_MIN                         (KI_MIN                         )
) u_pi_ctrl ( 
        .rst_n                          (rst_n                          ),
        .clk                            (clk                            ),
        .cke                            (cke                            ),

        .in_vld                         (in_vld                         ),
        .in_dat                         (in_dat                         ),	// s(  IN_DW,   IN_FW)
        .out_vld                        (inc_vld                        ),
        .out_dat                        (inc_dat                        ),	// s( OUT_DW,  OUT_FW)

        .cfg_kp                         (cfg_kp                         ),	// range [KP_MIN:KP_MAX], gain 
        .cfg_ki                         (cfg_ki                         ),	// range [KI_MIN:KI_MAX], gain 

        .cfg_acc_clr                    (cfg_iacc_clr                   ),
        .cfg_acc_frz                    (cfg_iacc_frz                   ),
        .cfg_acc_ini                    (cfg_iacc_ini                   ),	// s(IINC_DW, IINC_FW)
        .sts_acc_rtv                    (sts_iacc_rtv                   )
);

acc #(
        .IN_DW                          (INC_DW                         ),	// IN_DW-IN_FW<=OUT_DW-OUT_FW
        .IN_FW                          (INC_FW                         ),
        .OUT_DW                         (OUT_DW                         ),
        .OUT_FW                         (OUT_FW                         )	// must OUT_FW<=IN_FW
) u_oacc ( 
        .rst_n                          (rst_n                          ),
        .clk                            (clk                            ),
        .cke                            (cke                            ),

        .in_vld                         (inc_vld                        ),
        .in_dat                         (inc_dat                        ),	// s( IN_DW,  IN_FW)
        .out_dat                        (out_dat                        ),	// s(OUT_DW, OUT_FW)

        .cfg_acc_clr                    (cfg_oacc_clr                   ),	// clear acc to cfg_ini
        .cfg_acc_frz                    (cfg_oacc_frz                   ),	// freeze acc, but cfg_frz is lower priority than cfg_clr
        .cfg_acc_ini                    (cfg_oacc_ini                   ),	// s(OUT_DW, OUT_FW)
        .sts_acc_rtv                    (sts_oacc_rtv                   ) 	// s( OUT_DW,  OUT_FW), real-time value
);

`include "func_param.v" // refer to MAX_F()

//################################################################################
// ASSERTION
//################################################################################

`ifdef CBB_ASSERT_ON
// synopsys translate_off

initial begin
    if (OUT_FW>OACC_FW) begin
        $error("out fractional width is bigger than OACC fractional width.");
        $stop;
    end

    if (OACC_FW>IN_FW+KI_MAX) begin
        $error("OACC fraction width is bigger than IN_FW+KI_MAX.");
        $stop;
    end

    if (PINC_DW>=OACC_DW) begin
        $error("PINC_DW is bigger than OACC_DW.");
        $stop;
    end

    if (PINC_DW>OACC_FW+IN_IW-KP_MIN) begin
        $error("PINC_DW width is bigger than OACC_FW+IN_IW-KP_MIN.");
        $stop;
    end

    if (IINC_DW>=OACC_DW) begin
        $error("IINC_DW width is bigger than OACC_DW.");
        $stop;
    end

    if ((OACC_FW>IACC_FW) || (IACC_FW>IN_FW+KI_MAX)) begin
        $error("IACC_FW is wrong.");
        $stop;
    end
end

// synopsys translate_on
`endif

endmodule
